/******************************************************************************

	COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2008, 2009, 2010.

	This file is part of Maxsi Engine.

	Maxsi Engine is free software: you can redistribute it and/or modify it
	under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or (at your
	option) any later version.

	Maxsi engine is distributed in the hope that it will be useful, but WITHOUT
	ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
	FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
	for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with Maxsi Engine. If not, see <http://www.gnu.org/licenses/>.

	MaxsiEngine.dll
	A general purpose C++ library for the Maxsi Technology Project.

	MaxsiNetworking.cpp
	Multithreaded Networking.

******************************************************************************/

#include "MaxsiEngine.h"

#ifdef Maxsi_Support_POSIX
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#include <sys/time.h>
#endif

#ifdef Maxsi_Support_Epoll
#include <sys/epoll.h>
#endif

BeginMaxsiNamespace

#if defined(Maxsi_Support_W32)
MaxsiNetworkingW32
#elif defined(Maxsi_Support_POSIX)
MaxsiNetworkingPOSIX
#else
#error "MaxsiNetworking is not implemented on this target platform!"
#endif
MyNetworking;

LINK MaxsiNetworking* Networking()
{
	return &MyNetworking;
}

#ifdef Maxsi_Support_POSIX
// 1.  Create a socket with the socket() system call
// 2. Connect the socket to the address of the server using the connect() system call
// 3. Send and receive data. There are a number of ways to do this, but the simplest is to use the read() and write() system calls.

MAXSI_TODO("According to POSIX I/O calls might fail if a signal happens during them. To avoid this on GNU systems, you have to wrap the calls in TEMP_FAILURE_RETRY. Please see http://www.ia.pw.edu.pl/~wujek/dokumentacja/gnu/libc/libc_21.html#SEC367");

MaxsiHandle MaxsiNetworkingPOSIX::SyncConnectToHost(MESTR* HostName, short Port)
{
	char*			HostNameSL			=	NULL;
	int				Socket				=	-1;
	hostent*		ServerAddressCopy	=	NULL;
	sockaddr_in		ServerAddress;

	// Create a handle.
	MaxsiHandle		Result		=	(MaxsiHandle)new int;

	// Check if the handle was created.
	bool			Success		=	(Result != NULL);
	
	if ( Success )
	{
		// Convert the Host Name to the system locale.
		HostNameSL	=	MESTR2CHAR(HostName);

		// Check for error conditions.
		if ( HostNameSL == NULL ) { Success = false; }
	}

	if ( Success )
	{
		// Create a socket.
		Socket		=	socket(AF_INET, SOCK_STREAM, 0);

		// Check for error conditions.
		if ( Socket == -1 ) { Success = false; }
	}

	if ( Success )
	{
		// Do the DNS Lookup.
		ServerAddressCopy	=	gethostbyname(HostNameSL);
		MAXSI_TODO("The Open Group doesn't require gethostbyname to be thread safe!");

		// Check for error conditions.
		if ( ServerAddressCopy == NULL || ServerAddressCopy->h_addr == NULL ) { Success = false; }
	}

	if ( Success )
	{	
		// Reset the Server Adress structure.
		memset(&ServerAddress, 0, sizeof(ServerAddress));

		// Set the address type.
		ServerAddress.sin_family = AF_INET;

		// Copy the server address.
		memcpy(&ServerAddress.sin_addr.s_addr, ServerAddressCopy->h_addr, ServerAddressCopy->h_length);

		// Convert the remote port to network order (endianness).
		ServerAddress.sin_port = htons(Port);

		// Connect to the remote host and check for error conditions.
		if ( connect(Socket, (sockaddr*)&ServerAddress, sizeof(ServerAddress)) != 0 ) { Success = false; }
	}

	if ( Success )
	{
		*(int*)Result	=	Socket;
	}

	IFDEL(HostNameSL);

	if ( Success == false ) { if ( Socket != -1 ) { close(Socket); } }
	if ( Success == false ) { delete (int*)Result; return NULL; }
	
	return Result;	
}

size_t MaxsiNetworkingPOSIX::SyncReadSocket(MaxsiHandle Socket, BYTE* Buffer, size_t Size)
{
	ssize_t Read	=	read(*(int*)Socket, Buffer, Size);

	if ( Read == -1 || Read > SSIZE_MAX ) { return 0; }

	if ( Read < 0 ) { Read = 0; } MAXSI_TODO("Test if this is correct behavior!");

	return (size_t)(Read);
}

size_t MaxsiNetworkingPOSIX::SyncWriteSocket(MaxsiHandle Socket, BYTE* Buffer, size_t Size)
{
	size_t		Left	=	Size;

	while ( Left )
	{
		ssize_t		Written		=	write(*(int*)Socket, Buffer, Size);

		if ( Written == -1 ) { return 0; }

		if ( Written < 0 ) { Written = 0; } MAXSI_TODO("Test if this is correct behavior!");

		Left	-=	(size_t)Written;
	}

	return Size;
}

bool MaxsiNetworkingPOSIX::SyncCloseSocket(MaxsiHandle Socket)
{
	close(*(int*)Socket);
	delete (int*)Socket;
	return true;
}

#endif

#ifdef Maxsi_Support_W32

MaxsiHandle MaxsiNetworkingW32::SyncConnectToHost(MESTR* HostName, short Port)
{
	char*			HostNameSL			=	NULL;
	SOCKET			Socket				=	INVALID_SOCKET;
	hostent*		ServerAddressCopy	=	NULL;
	sockaddr_in		ServerAddress;

	// Create a handle.
	MaxsiHandle		Result		=	(MaxsiHandle)new SOCKET;

	// Check if the handle was created.
	bool			Success		=	(Result != NULL);
	
	if ( Success )
	{
		// Convert the Host Name to the system locale.
		HostNameSL	=	MESTR2CHAR(HostName);

		// Check for error conditions.
		if ( HostNameSL == NULL ) { Success = false; }
	}

	if ( Success )
	{
		// Create a socket.
		Socket		=	socket(AF_INET, SOCK_STREAM, 0);

		// Check for error conditions.
		if ( Socket == INVALID_SOCKET ) { Success = false; }
	}

	if ( Success )
	{
		// Do the DNS Lookup.
		ServerAddressCopy	=	gethostbyname(HostNameSL);

		// Check for error conditions.
		if ( ServerAddressCopy == NULL || ServerAddressCopy->h_addr == NULL ) { Success = false; }
	}

	if ( Success )
	{	
		// Reset the Server Adress structure.
		memset(&ServerAddress, 0, sizeof(ServerAddress));

		// Set the address type.
		ServerAddress.sin_family = AF_INET;

		// Copy the server address.
		memcpy(&ServerAddress.sin_addr.s_addr, ServerAddressCopy->h_addr, ServerAddressCopy->h_length);

		// Convert the remote port to network order (endianness).
		ServerAddress.sin_port = htons(Port);

		// Connect to the remote host and check for error conditions.
		if ( connect(Socket, (sockaddr*)&ServerAddress, sizeof(ServerAddress)) != 0 ) { Success = false; }
	}

	if ( Success )
	{
		*(SOCKET*)Result	=	Socket;
	}

	IFDEL(HostNameSL);

	if ( Success == false ) { if ( Socket != INVALID_SOCKET ) { closesocket(Socket); } }
	if ( Success == false ) { delete (SOCKET*)Result; return NULL; }
	
	return Result;	
}

size_t MaxsiNetworkingW32::SyncReadSocket(MaxsiHandle Socket, BYTE* Buffer, size_t Size)
{
	int Read	=	recv(*(SOCKET*)Socket, (char*)Buffer, Size, 0);

	if ( Read == SOCKET_ERROR || Read == 0 ) { return 0; }

	if ( Read < 0 ) { Read = 0; } MAXSI_TODO("Test if this is correct behavior!");

	return (size_t)(Read);
}

size_t MaxsiNetworkingW32::SyncWriteSocket(MaxsiHandle Socket, BYTE* Buffer, size_t Size)
{
	size_t		Left	=	Size;

	while ( Left )
	{
		int	Written		=	send(*(SOCKET*)Socket, (char*)Buffer, Size, 0);

		if ( Written == SOCKET_ERROR ) { return 0; }

		if ( Written < 0 ) { Written = 0; } MAXSI_TODO("Test if this is correct behavior!");

		Left	-=	(size_t)Written;
	}

	return Size;
}

bool MaxsiNetworkingW32::SyncCloseSocket(MaxsiHandle Socket)
{
	closesocket(*(SOCKET*)Socket);
	delete (SOCKET*)Socket;
	return true;
}

#endif

#define Maxsi_RetrieveHTTP_Buffer_Size 16384

MaxsiError RetrieveHTTP(MESTR* Host, MESTR* Resource, MaxsiNetworkingSyncCallback Callback, void* UserData, short Port, MaxsiNetworking* Network)
{
	MAXSI_TODO("This implementation does not follow the official RFC and is not standards-compliant!");

	// Generate the HTTP/1.1 request.
	MESTR*	Request			=	BuildString(5, _MESTR("GET "), Resource, _MESTR(" HTTP/1.1\r\nHost: "), Host, _MESTR("\r\n\r\n"));
	
	// Check for error conditions.
	if ( Request == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	// Convert the HTTP request to ASCII.
	ASCII_t*	RequestA	=	MESTR2ASCII(Request);

	// Delete the MESTR request.
	delete[] Request;

	//Check for error conditions.
	if ( RequestA == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	// Find the size of our request.
	size_t	RequestSize		=	sizeof(ASCII_t)*strlen((char*)RequestA);

	// If we have not been assigned a specific networking class, then just use the default.
	if ( Network == NULL ) { Network = Networking(); }

	// Connect to the remote host.
	MaxsiHandle	Socket = Network->SyncConnectToHost(Host, Port);

	// Check for error conditions.
	if ( Socket == NULL ) { delete[] RequestA; return MAXSI_ERROR_COULD_NOT_CONNECT; }

	// Define the result variable.
	MaxsiError	Result	=	MAXSI_ERROR_SUCCESS;

	// Write our request and check for error conditions.
	if ( Network->SyncWriteSocket(Socket, (BYTE*)RequestA, RequestSize) != RequestSize) { Result = MAXSI_ERROR_CONNECTION_ISSUE; }

	// Delete the ascii request.
	delete[] RequestA;

	if ( Result == MAXSI_ERROR_SUCCESS )
	{
		BYTE Buffer[Maxsi_RetrieveHTTP_Buffer_Size+1];

		size_t	Read	=	1;

		while ( Read )
		{
			// Read on our socket.
			Read = Network->SyncReadSocket(Socket, Buffer, Maxsi_RetrieveHTTP_Buffer_Size);

			if ( Read )
			{	
				Buffer[Read]							=	0;
				Buffer[Maxsi_RetrieveHTTP_Buffer_Size]	=	0;

				Callback(UserData, Buffer, Read);
			}
		}
	}

	// Close the socket.
	Network->SyncCloseSocket(Socket);

	return Result;	
}


#if defined(Maxsi_Support_Linux)

#ifdef Maxsi_Support_Epoll
MaxsiAsyncEpoll
#else
//#error "There is no async IO handler installed on this system!"
#endif
MyAsync;

MaxsiAsync* Async()
{
	return &MyAsync;
}

#ifdef Maxsi_Support_Epoll

// TODO: The epoll interface, when used with the EPOLLET flag ( Edge Triggered ) should use non-blocking file descriptors to avoid having a blocking read or write starve the task that is handling multiple file descriptors.

// TODO: Okay. When we get an event, set the flag that we can do IO, then do IO until we fail, and then set the flag that we can't, and then listen for events again.

MaxsiAsyncEpoll::MaxsiAsyncEpoll()
{
	EpollFD		=	epoll_create(Maxsi_Epoll_DefaultSize); // Returns -1 on failure.
}

MaxsiAsyncEpoll::~MaxsiAsyncEpoll()
{
	if ( EpollFD != -1 ) { close(EpollFD); }	
}

MaxsiHandle MaxsiAsyncEpoll::AsyncListen(short Port)
{
	int				Socket				=	-1;
	sockaddr_in		ServerAddress;

	// Create a handle.
	MaxsiHandle		Result		=	(MaxsiHandle)new MaxsiAsyncEpollIOHandle;

	// Check if the handle was created.
	bool			Success		=	(Result != NULL);
	
	if ( Success )
	{
		memset(Result, 0, sizeof(MaxsiAsyncEpollIOHandle));
		gettimeofday(&(((MaxsiAsyncEpollIOHandle*)Result)->Begun), NULL);
		gettimeofday(&(((MaxsiAsyncEpollIOHandle*)Result)->Last), NULL);

		((MaxsiAsyncEpollIOHandle*)Result)->State	=	MAXSI_EPOLL_IS_SOCKET_LISTENER;

		// Create a socket.
		Socket		=	socket(AF_INET, SOCK_STREAM, 0);

		// Check for error conditions.
		if ( Socket == -1 ) { Success = false; }
	}

	if ( Success )
	{
		memset(&ServerAddress, 0, sizeof(ServerAddress));

		ServerAddress.sin_family = AF_INET;
		ServerAddress.sin_addr.s_addr = INADDR_ANY;
		ServerAddress.sin_port = htons(Port);

		if ( bind(Socket, (struct sockaddr*)&ServerAddress, sizeof(ServerAddress)) < 0 )
		{
			PrintOutput("Could not bind socket!\n");
			Success = false;
		}
	}

	if ( Success )
	{
		int flags;
		// Set the socket as non-blocking.
		if ( (flags = fcntl(Socket, F_GETFL, 0)) < 0 || fcntl(Socket, F_SETFL, flags | O_NONBLOCK) < 0 )
		{
			PrintOutput("Could not set socket as non-blocking.\n");
			Success	= false;
		}
	}

	if ( Success )
	{
		listen(Socket, 5); MAXSI_TODO("Find better hardcoded constant!");
	}

	if ( Success )
	{
		((MaxsiAsyncEpollIOHandle*)Result)->FD	=	Socket;
	}

	if ( Success )
	{
		if ( SetUpAsyncIOHandleCallbacks(Result) != MAXSI_ERROR_SUCCESS ) { Success = false; }
	}

	if ( Success )
	{
		PrintOutput("Successfully listening at port %u\n", (unsigned int)Port);
	}
	else
	{
		PrintOutput("Could not listen at port %u\n", (unsigned int)Port);
	}

	if ( Success == false ) { if ( Socket != -1 ) { close(Socket); } }
	if ( Success == false ) { delete (MaxsiAsyncEpollIOHandle*)Result; return NULL; }

	return Result;	
}

MaxsiHandle MaxsiAsyncEpoll::AsyncConnect(MESTR* HostName, short Port)
{
	char*			HostNameSL			=	NULL;
	int				Socket				=	-1;
	hostent*		ServerAddressCopy	=	NULL;
	sockaddr_in		ServerAddress;

	// Create a handle.
	MaxsiHandle		Result		=	(MaxsiHandle)new MaxsiAsyncEpollIOHandle;

	// Check if the handle was created.
	bool			Success		=	(Result != NULL);
	
	if ( Success )
	{
		memset(Result, 0, sizeof(MaxsiAsyncEpollIOHandle));
		gettimeofday(&(((MaxsiAsyncEpollIOHandle*)Result)->Begun), NULL);
		gettimeofday(&(((MaxsiAsyncEpollIOHandle*)Result)->Last), NULL);

		// Convert the Host Name to the system locale.
		HostNameSL	=	MESTR2CHAR(HostName);

		// Check for error conditions.
		if ( HostNameSL == NULL ) { Success = false; }
	}

	if ( Success )
	{
		// Create a socket.
		Socket		=	socket(AF_INET, SOCK_STREAM, 0);

		// Check for error conditions.
		if ( Socket == -1 ) { Success = false; }
	}

	if ( Success )
	{
		int flags;
		// Set the socket as non-blocking.
		if ( (flags = fcntl(Socket, F_GETFL, 0)) < 0 || fcntl(Socket, F_SETFL, flags | O_NONBLOCK) < 0 )
		{
			PrintOutput("Could not set socket as non-blocking.\n");
			Success	= false;
		}
	}

	if ( Success )
	{
		// Do the DNS Lookup.
		ServerAddressCopy	=	gethostbyname(HostNameSL);
		MAXSI_TODO("gethostbyname is blocking!");
		MAXSI_TODO("The Open Group doesn't require gethostbyname to be thread safe!");

		// Check for error conditions.
		if ( ServerAddressCopy == NULL || ServerAddressCopy->h_addr == NULL ) { Success = false; }
	}

	if ( Success )
	{	
		// Reset the Server Adress structure.
		memset(&ServerAddress, 0, sizeof(ServerAddress));

		// Set the address type.
		ServerAddress.sin_family = AF_INET;

		// Copy the server address.
		memcpy(&ServerAddress.sin_addr.s_addr, ServerAddressCopy->h_addr, ServerAddressCopy->h_length);

		// Convert the remote port to network order (endianness).
		ServerAddress.sin_port = htons(Port);

		// Connect to the remote host and check for error conditions.	
		if ( connect(Socket, (sockaddr*)&ServerAddress, sizeof(ServerAddress)) != 0 && errno != EINPROGRESS ) { PrintOutput("'connect' failed with %i!\n", errno); Success = false; }
	}

	if ( Success )
	{
		((MaxsiAsyncEpollIOHandle*)Result)->FD	=	Socket;
	}

	if ( Success )
	{
		if ( SetUpAsyncIOHandleCallbacks(Result) != MAXSI_ERROR_SUCCESS ) { Success = false; }
	}

	if ( Success )
	{
		PrintOutput("Successfully connected to remote!\n");
	}
	else
	{
		PrintOutput("Could not connect to remote!\n");
	}

	IFDEL(HostNameSL);

	if ( Success == false ) { if ( Socket != -1 ) { close(Socket); } }
	if ( Success == false ) { delete (MaxsiAsyncEpollIOHandle*)Result; return NULL; }

	return Result;
}

MaxsiHandle MaxsiAsyncEpoll::AsyncOpenFile(MESTR* Path, FLAGS32 Mode)
{
	PrintOutput("Begun opening file!\n");

	// Convert the Path to the system locale!
	char*	PathSL	=	MESTR2CHAR(Path);

	// Check for error conditions.
	if ( PathSL == NULL ) { PrintOutput("Could not convert MESTR to char!\n"); return NULL; }	

	// Allocate handlefull of our favorite kind of local AsyncIOHandles!
	MaxsiAsyncEpollIOHandle*	Handle	=	new MaxsiAsyncEpollIOHandle;

	// Check for error conditions.
	if ( Handle == NULL ) { PrintOutput("Could not allocate a handle structure!\n"); delete[] PathSL; return NULL; }

	int		Flags	=	O_LARGEFILE | O_NONBLOCK;

	if ( (Mode & MAXSI_FILE_READ) && !(Mode & MAXSI_FILE_WRITE) ) { Flags |= O_RDONLY; }
	if ( !(Mode & MAXSI_FILE_READ) && (Mode & MAXSI_FILE_WRITE) ) { Flags |= O_WRONLY; }
	if ( (Mode & MAXSI_FILE_READ) && (Mode & MAXSI_FILE_WRITE) ) { Flags |= O_RDWR; }
	if ( Mode & MAXSI_FILE_CREATE ) { Flags = O_CREAT; }
	if ( Mode & MAXSI_FILE_TRUNCATE ) { Flags = O_TRUNC; }
	// Not available under Linux: if ( Mode & MAXSI_FILE_SEQUENTIAL ) { Flags |= FILE_FLAG_SEQUENTIAL_SCAN; }

	MAXSI_TODO("The open call is actually not async!");

	Handle->FD		=	open(PathSL, Flags);

	delete[] PathSL;

	if ( Handle->FD == -1 ) { PrintOutput("Could not open file with errono=%i!\n", errno); delete Handle; return NULL; }

	if ( SetUpAsyncIOHandleCallbacks((MaxsiHandle)Handle) != MAXSI_ERROR_SUCCESS ) { PrintOutput("Could not set up async callbacks on file '%s'!\n", PathSL); close(Handle->FD); delete Handle; return NULL; }

	PrintOutput("Correctly opened file.\n");

	return (MaxsiHandle)Handle;
}

MaxsiError MaxsiAsyncEpoll::SetUpAsyncIOHandleCallbacks(MaxsiHandle IOHandle)
{
	if ( EpollFD == -1 ) { MAXSI_TODO("Find a better error code."); return MAXSI_ERROR_UNSPECIFIED; }

	epoll_event Event;

	MaxsiAsyncEpollIOHandle*	Handle	=	(MaxsiAsyncEpollIOHandle*)IOHandle;

	memset(&Event, 0, sizeof(Event));

	Event.data.fd	=	Handle->FD;
	Event.data.ptr	=	(void*)Handle;
	Event.events	=	EPOLLERR | EPOLLET | EPOLLIN | EPOLLOUT | EPOLLHUP;

	int	Result = epoll_ctl(EpollFD, EPOLL_CTL_ADD, Handle->FD, &Event);

	if ( Result != 0 ) { PrintOutput("epoll_ctl failed with errno=%i on FD=%i\n", errno, Handle->FD); MAXSI_TODO("Find a better error code."); return MAXSI_ERROR_UNSPECIFIED; }

	return MAXSI_ERROR_SUCCESS;
}

#define Maxsi_Epoll_Concurrent_Event_Listens 64

MaxsiError MaxsiAsyncEpoll::MainLoop()
{
	PrintOutput("Entering mainloop.\n");

	size_t			EventsSize	=	Maxsi_Epoll_Concurrent_Event_Listens;
	epoll_event*	Events		=	new epoll_event[EventsSize];

#if 0
	BYTE			Buffer[4096];
	size_t			Size		=	4096;
	size_t			TotalRead	=	0;

	char*			WriteBuffer	=	"GET /Altered%20Transmission/Altered%20Transmission%20-%20Demo%209.zip HTTP/1.1\r\nHost: www.maxsi.dk\r\n\r\n";
	size_t			WriteSize	=	sizeof(char)*strlen(WriteBuffer);
	size_t			WriteLeft	=	WriteSize;
#endif

	timeval	LoopBegun;
	timeval	LoopLast;
	timeval	LoopNow;

	gettimeofday(&LoopBegun, NULL);
	gettimeofday(&LoopLast, NULL);
	gettimeofday(&LoopNow, NULL);

	FileSize_t		SumWritten	=	0;

	if ( Events == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	while ( true )
	{
		int	NumEvents	=	epoll_wait(EpollFD, Events, EventsSize, -1);

		if ( NumEvents == -1 ) { PrintOutput("Error in epoll_wait!\n"); MAXSI_TODO("Find a better error code."); return MAXSI_ERROR_UNSPECIFIED; }
	
		if ( NumEvents == 0 ) { PrintOutput("Help! I'm stuck in a busy wait!\n"); }

		for ( int I = 0; I < NumEvents; I++ )
		{
			MaxsiAsyncEpollIOHandle*	Handle	=	(MaxsiAsyncEpollIOHandle*)(Events[I].data.ptr);
			
			gettimeofday(&(Handle->Now), NULL);

			Handle->NowMS = (Handle->Now.tv_sec)*1000 + (Handle->Now.tv_usec/1000);
			Handle->LastMS = (Handle->Last.tv_sec)*1000 + (Handle->Last.tv_usec/1000);
			Handle->BegunMS = (Handle->Begun.tv_sec)*1000 + (Handle->Begun.tv_usec/1000);
			Handle->Elapsed = (float)(Handle->NowMS - Handle->BegunMS)/1000.f;

			Handle->PrintDebug = ( Handle->NowMS > Handle->LastMS + 1000 && (Handle->Elapsed > 0.1f) );

			if ( Handle->PrintDebug ) { PrintOutput("\r"); }

			FileSize_t	WrittenDiff		=	Handle->TotalWritten;

			if ( Handle->State & MAXSI_EPOLL_IS_SOCKET_LISTENER )
			{
				/*while ( */AcceptClient(Handle) /*== MAXSI_ERROR_SUCCESS ) { }*/;
			}
			else
			{	
				if ( Events[I].events & EPOLLERR ) { PrintOutput("An error occured on FD=%i.\n", Handle->FD); }
				if ( Events[I].events & EPOLLHUP ) { PrintOutput("A hangup occured on FD=%i.\n", Handle->FD); }
				if ( Events[I].events & EPOLLIN ) { DoRead(Handle); }
				if ( Events[I].events & EPOLLOUT ) { DoWrite(Handle); }
			}

			SumWritten		+=	Handle->TotalWritten - WrittenDiff;

			gettimeofday(&LoopNow, NULL);

			if ( true && Handle->State & MAXSI_EPOLL_IS_SERVER && LoopNow.tv_sec > LoopLast.tv_sec )
			{
				PrintOutput("Server performance: %.2f MiB/s\n", ((float)SumWritten)/1024.f/1024.f);
				LoopLast	=	LoopNow;
				SumWritten	=	0;
			}

			if ( false && Handle->PrintDebug && Handle->State & MAXSI_EPOLL_IS_SERVER && Handle->Elapsed )
			{
				float 	WrittenMiB	=	SumWritten/1024.f/1024.f;
				float	Speed		=	SumWritten / Handle->Elapsed;
				float	SpeedMiB	=	Speed/1024.f/1024.f;
				float	SpeedMbits	=	SpeedMiB*8.0f;

				PrintOutput("\rWritten %.0f MiB %.0f MiB/s (%.0f Mbits/s) over %lu s", WrittenMiB, SpeedMiB, SpeedMbits, (Handle->NowMS - Handle->BegunMS)/1000);
			} 

			//if ( Handle->PrintDebug && Handle->State & MAXSI_EPOLL_STATE_READ ) { PrintOutput("R "); }
			//if ( Handle->PrintDebug && Handle->State & MAXSI_EPOLL_STATE_WRITE ) { PrintOutput("W "); }

			//if ( Handle->PrintDebug ) { PrintOutput(".      "); gettimeofday(&Handle->Last, NULL); }
			//if ( Handle->PrintDebug ) { v("\n"); gettimeofday(&Handle->Last, NULL); }
		}
	}

	PrintOutput("Left mainloop!\n");

	delete[] Events;	

	return MAXSI_ERROR_SUCCESS;
}


MaxsiError MaxsiAsyncEpoll::DoRead(MaxsiAsyncEpollIOHandle* Handle)
{
	size_t	LocalRead	=	Handle->TotalRead;

	while ( true )
	{
		ssize_t Read		=	read(Handle->FD, Handle->InBuf, MAXSI_EPOLL_BUF_SIZE);
	
		if ( Read == -1 && errno == EAGAIN ) { break; }
		{
			if ( Read == -1 || Read > SSIZE_MAX ) { PrintOutput("There was an error condition on FD=%i during reading!\n", Handle->FD); }

			if ( Read < 0 ) { Read = 0; } MAXSI_TODO("Test if this is correct behavior!");

			Handle->TotalRead	+=	Read;
		}
	}

	LocalRead	=	Handle->TotalRead - LocalRead;

	if ( LocalRead > 0 )
	{
		if ( Handle->PrintDebug )
		{
			float 	ReadMiB		=	Handle->TotalRead/1024.f/1024.f;
			float	Speed		=	Handle->TotalRead / Handle->Elapsed;
			float	SpeedMiB	=	Speed/1024.f/1024.f;
			float	SpeedMbits	=	SpeedMiB*8.0f;

			PrintOutput("Read FD=%i %.3f MiB (%.3f MiB/s) (%.3f Mbits/s) ", Handle->FD, ReadMiB, SpeedMiB, SpeedMbits);
			//PrintOutput("Read %zu (%.0f MiB) %.1f MiB/s (%.1f Mbits/s) ", LocalRead, ReadMiB, SpeedMiB, SpeedMbits);
		}
	}

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiAsyncEpoll::DoWrite(MaxsiAsyncEpollIOHandle* Handle)
{
	if ( Handle->State & MAXSI_EPOLL_IS_SERVER )
	{
		while ( true )
		{
			ssize_t Written	=	write(Handle->FD, Handle->OutBuf, MAXSI_EPOLL_BUF_SIZE);
			
			if ( Written == -1 && errno == EAGAIN ) { break; }

			if ( Written == -1 || Written > SSIZE_MAX ) { PrintOutput("There was an error condition on the IO Handle during reading!\n"); }

			if ( Written < 0 ) { Written = 0; } MAXSI_TODO("Test if this is correct behavior!");

			if ( Written > 0 ) { Handle->TotalWritten += Written; }
		}
	}
	else
	{
		//if ( Handle->PrintDebug ) { PrintOutput("Not Server: Wrote 1 B! "); }
		//write(Handle->FD, Handle->OutBuf, 1);
		Handle->State |= MAXSI_EPOLL_STATE_WRITE;
	}

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiAsyncEpoll::AcceptClient(MaxsiAsyncEpollIOHandle* Handle)
{
	PrintOutput("Incoming connection at listener FD=%i.\n", Handle->FD);

	// Allocate handlefull of our favorite kind of local AsyncIOHandles!
	MaxsiAsyncEpollIOHandle*	NewHandle	=	new MaxsiAsyncEpollIOHandle;

	if ( NewHandle == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	NewHandle->TotalRead = 0;
	NewHandle->TotalWritten = 0;
	NewHandle->PrintDebug = false;
	NewHandle->NowMS = 0;
	NewHandle->LastMS = 0;
	NewHandle->BegunMS = 0;
	NewHandle->Elapsed = 0.0f;

	gettimeofday(&(NewHandle->Begun), NULL);
	gettimeofday(&(NewHandle->Now), NULL);
	gettimeofday(&(NewHandle->Last), NULL);

#if 0
	for ( size_t I = 0; I < MAXSI_EPOLL_BUF_SIZE; I++ )
	{
		NewHandle->OutBuf[I]	=	rand()*255/RAND_MAX;
	}
#endif

	NewHandle->State			=	MAXSI_EPOLL_IS_SERVER;

	socklen_t	AddrLen	=	0;
	sockaddr	Addr;

	NewHandle->FD				=	accept(Handle->FD, (sockaddr*)&Addr, &AddrLen);

	PrintOutput("\rI has accepted client FD=%i\n", NewHandle->FD);

	if ( NewHandle->FD < 0 ) { delete NewHandle; return MAXSI_ERROR_UNSPECIFIED; }

	int flags;

	if ( (flags = fcntl(NewHandle->FD, F_GETFL, 0)) < 0 || fcntl(NewHandle->FD, F_SETFL, flags | O_NONBLOCK) < 0 )
	{
		PrintOutput("Could not set client %i as non-blocking!\n", NewHandle->FD);
		MAXSI_TODO("Check return value."); 
	}

	if ( SetUpAsyncIOHandleCallbacks((MaxsiHandle)NewHandle) != MAXSI_ERROR_SUCCESS )
	{
		PrintOutput("Could not set callbacks for client %i!\n", NewHandle->FD);
		MAXSI_TODO("Check return value.");	
	}

	listen(Handle->FD, 5);

	return MAXSI_ERROR_SUCCESS;
}

#endif

#endif


EndMaxsiNamespace
